package joc.internal.transformer;

import java.lang.annotation.Annotation;

import javassist.CannotCompileException;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;
import joc.internal.EvaluationResult;
import joc.internal.Evaluator;
import joc.internal.compiler.BooleanExp;
import joc.internal.compiler.ConstructorExp;
import joc.internal.compiler.NestedExp;
import joc.internal.compiler.StandaloneExp;
import joc.internal.compiler.StaticCallExp;
import joc.internal.compiler.ValueExp;

public abstract class GetterSetterTransformer extends DeclaredMethodTransformer {

	public GetterSetterTransformer(Transformer transformer) {
		super(transformer);
	}

	public abstract Class<? extends Annotation> getAnnotationClass();

	public abstract int getExpectedNumberOfMethodArguments();

	@Override
	public void transform(CtClass clazz, CtMethod method) throws Exception {
		getLogger().debug("method " + method.getLongName());
		if (hasInheritedAnnotation(method, getAnnotationClass())) {
			String methodPrefix = getMethodPrefix(method);
			if (!method.getName().startsWith(methodPrefix)) {
				insertError("Method-name has to start with '" + methodPrefix + "'.", method, true);
				return;
			}
			if (!Character.isUpperCase(method.getName().charAt(methodPrefix.length()))) {
				insertError("First character after prefix '" + methodPrefix + "' has to be upper-case.", method, true);
				return;
			}
			String fieldName = method.getName().substring(methodPrefix.length(), methodPrefix.length() + 1)
					.toLowerCase()
					+ method.getName().substring(methodPrefix.length() + 1);
			CtField field;
			try {
				field = clazz.getField(fieldName);
			} catch (NotFoundException e) {
				insertError("Could not find expected field name " + fieldName + ".", method, true);
				return;
			}
			if (method.getParameterTypes().length != getExpectedNumberOfMethodArguments()) {
				insertError("Method is expected to have exactly " + getExpectedNumberOfMethodArguments()
						+ " argument(s).", method, true);
				return;
			}
			individualCheck(method, field);
		}
	}

	protected abstract void individualCheck(CtMethod method, CtField field) throws NotFoundException,
			CannotCompileException;

	protected abstract String getMethodPrefix(CtMethod method) throws Exception;

	public void insertError(String errorMessage, CtBehavior behavior, boolean insertFailure)
			throws CannotCompileException {
		transformer.getLogger().error(errorMessage);
		if (insertFailure) {
			NestedExp evaluationResult = new ConstructorExp(EvaluationResult.class, new ValueExp(
					getRecipientString(behavior)), new ValueExp(getAnnotationClass()), BooleanExp.FALSE, new ValueExp(
					errorMessage));
			StandaloneExp exp = new StaticCallExp(Evaluator.evaluateContract, evaluationResult).toStandalone();
			exp.insertBefore(behavior);
		}
	}
}
